home *** CD-ROM | disk | FTP | other *** search
- /*----------------------------------------------------------------------------
-
- news.c
-
- This module handles all transactions with the NNTP server. It acts as
- an interface between the rest of NewsWatcher and the resuable nntp.c
- module.
-
- Copyright © 1994, Northwestern University.
-
- ----------------------------------------------------------------------------*/
-
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
-
- #include "packages.h"
-
- #include "glob.h"
- #include "dialog.h"
- #include "news.h"
- #include "nntp.h"
- #include "status.h"
- #include "newswatcher.h"
- #include "charset.h"
- #include "text.h"
- #include "strutil.h"
- #include "memutil.h"
- #include "cache.h"
- #include "windutil.h"
- #include "resutil.h"
- #include "fileutil.h"
- #include "ic.h"
-
-
-
- #define kPostAuthDlg 136
- #define kStartupAuthDlg 137
- #define kAuthUsername 5
- #define kAuthPassword 7
-
- #define kFileBufLen (10*1024)
-
-
- static NntpStreamHandle gNewsStream = nil; /* handle to news server NNTP stream */
-
- /* Globals used by CopyArticleToFile. */
-
- static short gFileRefNum; /* file ref num */
- static Ptr gFileBuf = nil; /* pointer to output buffer */
- static Ptr gFileBufAux = nil; /* pointer to auxiliary output buffer */
- static long gFileBufAuxSize; /* number of bytes in aux buffer */
- static long gFileBufPos; /* current position in output buffer */
- static long gFileLinePos; /* position in current line */
- static char gFileLastChar; /* last char */
- static Boolean gFileTruncate; /* true to truncate if attached file */
- static Boolean gFileTruncated; /* true if attached file found, should truncate */
- static TAttachedFileKind gFileKind; /* kind of attached file */
- static Boolean gRequireEncodedTextBeginLine; /* true if attached file must
- include "begin" flag line */
-
-
-
- /*----------------------------------------------------------------------------
- ReportServerError
-
- Report server error to user.
-
- Entry: nntpErr = NNTP error as returned by nntp.c.
-
- Entry: function result = error code.
-
- If the NNTP error was an OS error, the error code is returned and it is
- the responsibility of the calling code to report it.
-
- If the NNTP error was a server error, the error is reported to the user,
- and the function result is userCanceledErr.
- ----------------------------------------------------------------------------*/
-
- static OSErr ReportServerError (NntpErr nntpErr)
- {
- CStr255 cmd, msg;
- long num;
- OSErr err = noErr;
-
- if (nntpErr == nntpServerErr) {
- NntpGetServerErrInfo(cmd, &num, msg);
- err = ServerErrorMessage(kStrNews, cmd, msg);
- if (err != noErr) return err;
- return userCanceledErr;
- } else if (nntpErr == nntpOSErr) {
- return NntpGetOSErr();
- }
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- SetServerOptions
-
- Set news server options from preferences.
-
- Exit: options = current server options.
- ----------------------------------------------------------------------------*/
-
- static void SetServerOptions (NntpStreamOptions *options)
- {
- MyICReadSharedPrefs(kICNewsAuthUsername);
- MyICReadSharedPrefs(kICNewsAuthPassword);
-
- options->idleTime = 10;
- options->useXPAT = gPrefs.useXPAT;
- options->sendModeReader = !gPrefs.noModeReader;
- options->batchedCmds = gPrefs.batchedGroupCmds;
- options->newConnection = !gPrefs.noNewConnection;
- options->authOnConnect = gPrefs.authAtStartup;
- strcpy(options->username, gPrefs.authUsername);
- strcpy(options->password, gPrefs.authPassword);
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetAuthInfo
-
- Get authorization information.
-
- Entry: dlgID = dialog id.
- statusMsg = status message, C-format, or nil if startup call.
-
- Exit: function result = error code.
- gPrefs.authUsername = authorization username.
- gPrefs.authPassword = authorization password.
- ----------------------------------------------------------------------------*/
-
- static OSErr GetAuthInfo (short dlgID, char *statusMsg)
- {
- DialogPtr dlg = nil;
- short item;
- CStr255 tempStr;
- short len;
- char username[32];
- char password[32];
- OSErr err = noErr;
-
- MyICReadSharedPrefs(kICNewsAuthUsername);
- MyICReadSharedPrefs(kICNewsAuthPassword);
-
- if (*gPrefs.authUsername != 0 && *gPrefs.authPassword != 0) return noErr;
-
- err = MyGetNewDialog(dlgID, ok, cancel, &dlg);
- if (err != noErr) return err;
- RestoreMovableModalDialogPosition(dlg, gPrefs.authLoc);
- strcpy(username, gPrefs.authUsername);
- DlgSetCString(dlg, kAuthUsername, username);
- SetItemUSAsciiNoBlank(dlg, kAuthUsername);
- SetItemMaxLength(dlg, kAuthUsername, 31);
- strcpy(password, gPrefs.authPassword);
- len = strlen(password);
- memset(tempStr, '•', len);
- tempStr[len] = 0;
- DlgSetCString(dlg, kAuthPassword, tempStr);
- SetItemPassword(dlg, kAuthPassword, password);
- SetItemMaxLength(dlg, kAuthPassword, 31);
- if (*username == 0) {
- SelectDialogItemText(dlg, kAuthUsername, 0, 0);
- } else if (*password == 0) {
- SelectDialogItemText(dlg, kAuthPassword, 0, 0);
- } else {
- SelectDialogItemText(dlg, kAuthUsername, 0, 0x7fff);
- }
-
- do {
- DlgEnableItem(dlg, ok, *username != 0 && *password != 0);
- MyMovableModalDialog(dlg, DialogFilter, &item);
- if (item == kAuthUsername) DlgGetCString(dlg, item, username);
- } while (item != ok && item != cancel);
-
- if (item == ok) {
- strcpy(gPrefs.authUsername, username);
- strcpy(gPrefs.authPassword, password);
- MyICWriteSharedPrefs(kICNewsAuthUsername);
- MyICWriteSharedPrefs(kICNewsAuthPassword);
- }
- SaveMovableModalDialogPosition(dlg, &gPrefs.authLoc);
- err = DoClose(dlg);
- if (err != noErr) return err;
- if (item == ok && statusMsg != nil) {
- err = DisplayStatusMessage(statusMsg);
- if (err != noErr) return err;
- }
- return item == ok ? noErr : userCanceledErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- StartNNTP
-
- Open a connection to the news server.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- OSErr StartNNTP (void)
- {
- NntpErr nntpErr;
- NntpStreamOptions options;
- CStr255 cmd, response;
- long responseCode;
- OSErr err = noErr;
-
- MyICReadSharedPrefs(kICNNTPHost);
-
- while (true) {
- gNewsStream = nil;
- if (gPrefs.authAtStartup) {
- err = GetAuthInfo(kStartupAuthDlg, nil);
- if (err != noErr) return err;
- }
- err = DisplayStatusMessageNumber(kStrOpeningConnectionStatusMsg);
- if (err != noErr) return err;
- SetServerOptions(&options);
- p2cstr(gPrefs.newsServerName);
- nntpErr = NntpOpen((char*)gPrefs.newsServerName, &options, &gNewsStream);
- c2pstr((char*)gPrefs.newsServerName);
- if (gPrefs.authAtStartup && nntpErr == nntpServerErr) {
- NntpGetServerErrInfo(cmd, &responseCode, response);
- if (responseCode == 502) {
- *gPrefs.authPassword = 0;
- MyICWriteSharedPrefs(kICNewsAuthPassword);
- gNewsStream = nil;
- err = ServerErrorMessage(kStrNews, cmd, response);
- if (err != noErr) return err;
- } else {
- goto exit;
- }
- } else if (nntpErr != nntpNoErr) {
- goto exit;
- } else {
- break;
- }
- }
- return noErr;
-
- exit:
-
- gNewsStream = nil;
- return ReportServerError(nntpErr);
- }
-
-
-
- /*----------------------------------------------------------------------------
- EndNNTP
-
- Close the connection to the NNTP server.
- ----------------------------------------------------------------------------*/
-
- void EndNNTP (void)
- {
- if (gNewsStream != nil) NntpClose(gNewsStream);
- gNewsStream = nil;
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetGroupNames
-
- Get a list of group names from the server.
-
- Entry: lastTime = 0: Fetch the entire full group list.
- lastTime != 0: Fetch just the groups which have been created
- since lastTime - 36 hours.
-
- Exit: function result = error code.
- *strings = handle to C-format group name strings.
- *numGroups = number of group names.
-
- The group names are mapped from Latin-1 to the Mac character set.
- The group names are truncated to 127 characters.
- Groups with status 'x' and '=' are filtered out (not returned).
- ----------------------------------------------------------------------------*/
-
- OSErr GetGroupNames (unsigned long lastTime, Handle *strings, long *numGroups)
- {
- NntpErr nntpErr;
- Handle theStrings;
- long theNumGroups;
-
- if (lastTime != 0) {
-
- /* Just get groups added since "lastTime". Subtract 36 hours to compensate
- for clock drift, daylight savings time, and server and client in
- different time zones. */
-
- lastTime -= 60L*60L*36L;
- }
-
- nntpErr = NntpGetGroupNames(gNewsStream, lastTime, "x=",
- &theStrings, &theNumGroups);
- if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
-
- MapLatin1ToMacHandle(theStrings);
- *strings = theStrings;
- *numGroups = theNumGroups;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetArticle
-
- Get one article from an NNTP server.
-
- Entry: host = address of news server, or nil to use default server.
- port = port number on news server if host not nil.
- groupName = name of group, or nil if fetching by message id.
- number = article number. Ignored if fetching by message id.
- id = message id string, including < and > delimiters. Ignored
- if fetching by article number.
- part = which part of the article to get:
- "ARTICLE": full article text, header and body.
- "HEAD": only article header.
- "BODY": only article body.
- truncateIfAttachedFile = true to truncate the article if it
- contains an attached file. The BinHex or uuencode text
- for the attached file is not returned.
- requireEncodedTextBeginLIne = true if BinHex or uuencode text
- must include special "begin" flag line.
-
- Exit: function result = error code.
- *text = handle to article text, or nil if article does not
- exit on server or group does not exist.
- *textLength = length of article text.
- *attachedFile = true if article contains an attached file which
- was truncated.
-
- Backspace-underscore and underscore-backspace sequences are filtered out
- of the article, as are all non-printable characters except for TAB and CR and FF.
- Tabs are expanded to 8 column tab stops.
- Trailing blank lines are deleted.
- CRLF line terminators are mapped to CR.
- Leading double-period characters on lines are mapped to single periods.
- 8 bit characters are mapped from Latin-1.
- ----------------------------------------------------------------------------*/
-
- OSErr GetArticle (char *host, short port,
- char *groupName, long number, char *id, char *part,
- Boolean truncateIfAttachedFile, Boolean requireEncodedTextBeginLine,
- Handle *text, long *textLength, Boolean *attachedFile)
- {
- NntpErr nntpErr;
- long len, newLen, col, nSpace;
- unsigned char **txt = nil;
- unsigned char *p, *pEnd, *q, *r;
- OSErr err = noErr;
- NntpStreamHandle stream = nil;
- CStr255 hostAndPortString;
-
- if (host == nil) {
- stream = gNewsStream;
- } else {
- sprintf(hostAndPortString, "%s,%d", host, port);
- nntpErr = NntpOpen(hostAndPortString, nil, &stream);
- if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
- }
- nntpErr = NntpGetArticle(stream, groupName, number, id, part,
- truncateIfAttachedFile, requireEncodedTextBeginLine,
- (Handle*)&txt, attachedFile);
- if (host != nil) {
- NntpClose(stream);
- }
- if (nntpErr != nntpNoErr) {
- if (nntpErr == nntpNoSuchGroupErr || nntpErr == nntpNoSuchArticleErr) {
- *text = nil;
- return noErr;
- } else {
- return ReportServerError(nntpErr);
- }
- }
-
- len = MyGetHandleSize(txt);
-
- for (p = *txt, pEnd = p + len, q = *txt; p < pEnd;) {
- if (p < pEnd-1 && ((*p == BS && *(p+1) == '_') || (*p == '_' && *(p+1) == BS))) {
- /* Filter underscore backspace and backspace underscore */
- p += 2;
- } else if (*p >= ' ' || *p == CR || *p == '\t' || *p == FF) {
- /* Copy printable character as is */
- *q++ = *p++;
- } else {
- /* Filter unprintable character */
- p++;
- }
- }
-
- /* Trim trailing blank lines */
-
- q--;
- while (q >= *txt && *q == CR) q--;
- q++;
-
- len = q - *txt;
- MySetHandleSize(txt, len);
-
- /* Map Latin1 */
-
- MapLatin1ToMacHandle((Handle)txt);
-
- /* Expand tabs. */
-
- newLen = len;
- r = *txt;
- for (p = *txt, pEnd = p + len; p < pEnd; p++) {
- if (*p == '\t') {
- col = p - r;
- nSpace = 8 - (col % 8);
- newLen += nSpace - 1;
- r = p+1;
- } else if (*p == CR) {
- r = p+1;
- }
- }
- if (newLen > len) {
- err = MySetHandleSize(txt, newLen);
- if (err != noErr) goto exit;
- BlockMoveData(*txt, *txt + newLen - len, len);
- r = *txt + newLen - len;;
- for (p = r, pEnd = p + len, q = *txt; p < pEnd; p++) {
- if (*p == '\t') {
- col = p - r;
- nSpace = 8 - (col % 8);
- while (nSpace--) *q++ = ' ';
- r = p+1;
- } else if (*p == CR) {
- *q++ = *p;
- r = p+1;
- } else {
- *q++ = *p;
- }
- }
- len = newLen;
- }
-
- *text = (Handle)txt;
- *textLength = len;
- return noErr;
-
- exit:
-
- MyDisposeHandle(txt);
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- TextHasAttachedFile
-
- Check article text to see if it includes an attached file.
-
- Entry: text = pointer to article text.
- length = length of text.
-
- Exit: *fileKind = kind of attached file.
- ----------------------------------------------------------------------------*/
-
- static void TextHasAttachedFile (Ptr text, long length, TAttachedFileKind *fileKind)
- {
- char *p, *pEnd, *q;
-
- for (p = text, pEnd = p + length; p < pEnd; p++) {
- if (*p == CR) {
- q = p+1;
- if (q >= pEnd) break;
- if (*q == '(') {
- if (strncmp(q, "(This file must be converted with BinHex", 39) == 0) {
- *fileKind = kBinHex;
- return;
- }
- } else if (*q == 'b') {
- if (strncmp(q, "begin ", 6) == 0) {
- q += 6;
- if (q >= pEnd) break;
- if (isoctal(*q) && isoctal(*(q+1)) && isoctal(*(q+2))) {
- *fileKind = kUUEncode;
- return;
- }
- }
- }
- }
- }
- *fileKind = kNoAttachedFile;
- }
-
-
-
- /*----------------------------------------------------------------------------
- CheckForAttachedFile
-
- Check for BinHex or UUEncode text.
-
- Entry: t = pointer to text.
- len = length of text.
- gRequireEncodedTextBeginLine = true if the encoded text
- must include a begin "flag" line.
-
- Exit: function result = true if BinHex or UUEncode text encountered.
- *pos = offset in text of beginning of BinHex or UUEncode text.
-
- This function is nearly identical to the one with the same name in
- nntp.c. They really should be combined some day.
- ----------------------------------------------------------------------------*/
-
- static Boolean CheckForAttachedFile (Ptr t, long len, long *pos)
- {
- long lenA, lenB;
- unsigned char *p, *pEnd, *q, *r;
- unsigned char *flagLine, *lineA, *lineB, *nextLine;
- Boolean haveBeginFlagLine, beginFlagLineIsBinHex, foundIt;
-
- p = (unsigned char*)t;
- pEnd = p + len - 1;
-
- while (p < pEnd && *p != CR) p++;
- p++;
- if (p >= pEnd) return false;
-
- while (p < pEnd) {
- if (gRequireEncodedTextBeginLine) {
- haveBeginFlagLine = false;
- flagLine = p;
- while (flagLine < pEnd) {
- q = flagLine;
- while (q < pEnd && *q != CR) q++;
- q += 1;
- if (q >= pEnd) break;
- if (*flagLine == '(') {
- if (flagLine + 39 >= pEnd) break;
- if (strncmp((char*)flagLine,
- "(This file must be converted with BinHex", 39) == 0)
- {
- haveBeginFlagLine = true;
- beginFlagLineIsBinHex = true;
- break;
- }
- } else if (*flagLine == 'b') {
- if (flagLine + 9 >= pEnd) break;
- if (strncmp((char*)flagLine, "begin ", 6) == 0) {
- r = flagLine + 6;
- if (isoctal(*r) && isoctal(*(r+1)) && isoctal(*(r+2))) {
- haveBeginFlagLine = true;
- beginFlagLineIsBinHex = false;
- break;
- }
- }
- }
- flagLine = q;
- }
- if (!haveBeginFlagLine) return false;
- p = flagLine;
- lineA = q;
- while (lineA < pEnd && *lineA == CR) lineA++;
- if (lineA >= pEnd) break;
- } else {
- lineA = p;
- }
- q = lineA;
- while (q < pEnd && *q != CR) q++;
- lenA = q - lineA;
- lineB = q + 1;
- while (lineB < pEnd && *lineB == CR) lineB++;
- if (lineB >= pEnd) break;
- nextLine = gRequireEncodedTextBeginLine ? lineA : lineB;
- if (lenA < 60) {
- p = nextLine;
- continue;
- }
- q = lineB;
- while (q < pEnd && *q != CR) q++;
- if (q >= pEnd) break;
- lenB = q - lineB;
- if (lenA != lenB) {
- p = nextLine;
- continue;
- }
- if (gRequireEncodedTextBeginLine) {
- if (beginFlagLineIsBinHex) {
- foundIt = IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB);
- } else {
- foundIt = IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB);
- }
- } else {
- foundIt = (IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB)) ||
- (IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB));
- }
- if (foundIt) {
- *pos = (char*)lineA - t;
- return true;
- } else {
- p = nextLine;
- continue;
- }
- }
-
- return false;
- }
-
-
-
- /*----------------------------------------------------------------------------
- FlushFileBuf
-
- Flush the file buffer.
-
- Entry: len = length of buffer.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- static OSErr FlushFileBuf (long len)
- {
- OSErr err = noErr;
- long headLen, tailLen, pos;
-
- MapLatin1ToMacPtr(gFileBuf, len);
-
- if (gFileTruncate) {
-
- if (gFileBufAuxSize > 0) {
- headLen = len > 200 ? 200 : len;
- BlockMoveData(gFileBuf, gFileBufAux + gFileBufAuxSize, headLen);
- gFileBufAuxSize += headLen;
- if (CheckForAttachedFile(gFileBufAux, gFileBufAuxSize, &pos)) {
- if (pos > 0) {
- err = MyFSWriteNoCache(gFileRefNum, &pos, gFileBufAux, GiveTime);
- if (err != noErr) return err;
- }
- gFileTruncated = true;
- return noErr;
- }
- gFileBufAuxSize -= headLen;
- err = MyFSWriteNoCache(gFileRefNum, &gFileBufAuxSize, gFileBufAux, GiveTime);
- if (err != noErr) return err;
- }
- if (CheckForAttachedFile(gFileBuf, len, &pos)) {
- if (pos > 0) {
- err = MyFSWriteNoCache(gFileRefNum, &pos, gFileBuf, GiveTime);
- if (err != noErr) return err;
- }
- gFileTruncated = true;
- return noErr;
- }
- tailLen = len > 200 ? 200 : len;
- len -= tailLen;
- BlockMoveData(gFileBuf + len, gFileBufAux, tailLen);
- gFileBufAuxSize = tailLen;
- if (len > 0) {
- err = MyFSWriteNoCache(gFileRefNum, &len, gFileBuf, GiveTime);
- if (err != noErr) return err;
- }
-
- } else {
-
- err = MyFSWriteNoCache(gFileRefNum, &len, gFileBuf, GiveTime);
- if (err != noErr) return err;
- if (gFileKind == kNoAttachedFile) {
- if (gFileBufAuxSize > 0) {
- headLen = len > 200 ? 200 : len;
- BlockMoveData(gFileBuf, gFileBufAux + gFileBufAuxSize, headLen);
- gFileBufAuxSize += headLen;
- TextHasAttachedFile(gFileBufAux, gFileBufAuxSize, &gFileKind);
- }
- if (gFileKind == kNoAttachedFile)
- TextHasAttachedFile(gFileBuf, len, &gFileKind);
- if (gFileKind == kNoAttachedFile) {
- tailLen = len > 200 ? 200 : len;
- BlockMoveData(gFileBuf + len - tailLen, gFileBufAux, tailLen);
- gFileBufAuxSize = tailLen;
- }
- }
-
- }
-
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- CopyArticleToFileChunkFunction
-
- Copy one article chunk to a file.
-
- Entry: t = pointer to chunk.
- tLen = length of chunk.
-
- Exit: function result = error code.
- *abort = true to abort the operation.
- ----------------------------------------------------------------------------*/
-
- static OSErr CopyArticleToFileChunkFunction (register Ptr t, register long tLen,
- Boolean *abort)
- {
- register char c, d;
- register Ptr p;
- Ptr pEnd;
- short nSpace;
- OSErr err = noErr;
-
- *abort = false;
-
- if (tLen == 0) return noErr;
-
- if (gFileLastChar != 0) {
- c = gFileLastChar;
- } else {
- c = *t;
- t++;
- tLen--;
- }
-
- p = gFileBuf + gFileBufPos;
- pEnd = gFileBuf + kFileBufLen - 10;
-
- while (tLen > 0) {
- if (p >= pEnd) {
- err = FlushFileBuf(p - gFileBuf);
- if (err != noErr) return err;
- if (gFileTruncated) {
- *abort = true;
- return noErr;
- }
- p = gFileBuf;
- }
- d = *t;
- if (c == BS && d == '_' || c == '_' && d == BS) {
- t++;
- c = *t;
- t++;
- tLen -= 2;
- } else if (c == CR && d == LF) {
- *p++ = CR;
- t++;
- c = *t;
- t++;
- tLen -= 2;
- gFileLinePos = 0;
- } else if (gFileLinePos == 0 && c == '.' && d == '.') {
- *p++ = '.';
- t++;
- c = *t;
- t++;
- tLen -= 2;
- gFileLinePos = 1;
- } else if (c == '\t') {
- nSpace = 8 - (gFileLinePos % 8);
- while (nSpace--) *p++ = ' ';
- gFileLinePos += nSpace;
- c = d;
- t++;
- tLen--;
- } else if (c >= ' ' || c == CR || c == FF || c < 0) {
- *p++= c;
- gFileLinePos++;
- c = d;
- t++;
- tLen--;
- } else {
- c = d;
- t++;
- tLen--;
- }
- }
-
- if (tLen == 0) {
- gFileLastChar = c;
- } else {
- gFileLastChar = 0;
- }
- gFileBufPos = p - gFileBuf;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- CopyArticleToFile
-
- Get one article from the NNTP server and copy it to a file.
-
- Entry: groupName = name of group, or nil if fetching by message id.
- number = article number. Ignored if fetching by message id.
- id = message id string, including < and > delimiters. Ignored
- if fetching by article number.
- part = which part of the article to get:
- "ARTICLE": full article text, header and body.
- "HEAD": only article header.
- "BODY": only article body.
- refNum = reference number of open file.
- truncateIfAttachedFile = true to truncate the article if it
- contains an attached file. The BinHex or uuencode text
- for the attached file is not copied to the file.
- requireEncodedTextBeginLine = true if BinHex or uuencode text
- must include special "begin" flag line.
-
- Exit: function result = error code.
- *fileKind = attached file kind, if !truncateIfAttachedFile.
-
- Backspace-underscore and underscore-backspace sequences are filtered out
- of the article, as are all non-printable characters except for TAB and CR and FF.
- Tabs are expanded to 8 column tab stops.
- CRLF line terminators are mapped to CR.
- Leading double-period characters on lines are mapped to single periods.
- 8 bit characters are mapped from Latin-1.
- ----------------------------------------------------------------------------*/
-
- OSErr CopyArticleToFile (char *groupName, long number, char *id, char *part,
- short refNum, Boolean truncateIfAttachedFile,
- Boolean requireEncodedTextBeginLine, TAttachedFileKind *fileKind)
- {
- NntpErr nntpErr;
- OSErr err = noErr;
- Boolean aborted;
-
- gFileRefNum = refNum;
- err = MyNewPtr(kFileBufLen, &gFileBuf);
- if (err != noErr) goto exit;
- err = MyNewPtr(400, &gFileBufAux);
- if (err != noErr) goto exit;
- gFileBufAuxSize = 0;
- gFileBufPos = 0;
- gFileLinePos = 0;
- gFileLastChar = 0;
- gFileTruncate = truncateIfAttachedFile;
- gFileTruncated = false;
- gFileKind = kNoAttachedFile;
- gRequireEncodedTextBeginLine = requireEncodedTextBeginLine;
-
- nntpErr = NntpGetChunkyArticle(gNewsStream, groupName, number, id, part,
- CopyArticleToFileChunkFunction, &aborted);
- if (nntpErr != nntpNoErr) {
- if (nntpErr == nntpNoSuchGroupErr || nntpErr == nntpNoSuchArticleErr) {
- *fileKind = kArtNotOnServer;
- goto exit;
- } else {
- err = ReportServerError(nntpErr);
- goto exit;
- }
- }
-
- if (!gFileTruncated) {
- if (gFileLastChar != 0) {
- *(gFileBuf + gFileBufPos) = gFileLastChar;
- gFileBufPos++;
- }
- if (gFileBufPos > 0) err = FlushFileBuf(gFileBufPos);
- if (gFileTruncate && gFileBufAuxSize > 0) {
- err = MyFSWriteNoCache(gFileRefNum, &gFileBufAuxSize, gFileBufAux, GiveTime);
- if (err != noErr) goto exit;
- }
- }
-
- *fileKind = gFileKind;
-
- exit:
-
- MyDisposePtr(gFileBuf);
- MyDisposePtr(gFileBufAux);
- gFileBuf = nil;
- gFileBufAux = nil;
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- PostArticle
-
- Post an article.
-
- Entry: text = handle to article text, including header.
- statusMsg = status message, C-format.
-
- Exit: function result = error code.
- postIndeterminate = true if entire article sent, but error occured
- or user canceled before final server response received. The
- article may or may not have been posted successfully.
- ----------------------------------------------------------------------------*/
-
- OSErr PostArticle (Handle text, char *statusMsg, Boolean *postIndeterminate)
- {
- NntpErr nntpErr;
- CStr255 cmd, response;
- long responseCode;
- OSErr err = noErr;
-
- MyICReadSharedPrefs(kICNewsAuthUsername);
- MyICReadSharedPrefs(kICNewsAuthPassword);
-
- nntpErr = NntpPostArticle(gNewsStream, text, postIndeterminate);
- if (nntpErr == nntpServerErr) {
- NntpGetServerErrInfo(cmd, &responseCode, response);
- if (responseCode == 480) {
- while (true) {
- err = GetAuthInfo(kPostAuthDlg, statusMsg);
- if (err != noErr) return err;
- nntpErr = NntpAuthorize(gNewsStream, gPrefs.authUsername, gPrefs.authPassword);
- if (nntpErr == nntpServerErr) {
- NntpGetServerErrInfo(cmd, &responseCode, response);
- if (responseCode == 502) {
- err = ServerErrorMessage(kStrNews, cmd, response);
- if (err != noErr) return err;
- *gPrefs.authPassword = 0;
- } else {
- goto exit;
- }
- } else if (nntpErr != nntpNoErr) {
- goto exit;
- } else {
- break;
- }
- }
- nntpErr = NntpPostArticle(gNewsStream, text, postIndeterminate);
- if (nntpErr != nntpNoErr) goto exit;
- } else {
- goto exit;
- }
- } else if (nntpErr != nntpNoErr) {
- goto exit;
- }
- return noErr;
-
- exit:
-
- return ReportServerError(nntpErr);
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetGroupArticleRange
-
- Query the NNTP server to get the current article range for a single group.
-
- Entry: theGroup = pointer to group record.
-
- Exit: function result = error code.
- *groupExits = true if group exists.
-
- theGroup->firstMess = first article in range.
- theGroup->lastMess = last article in range.
- theGroup->numUnread = number of articles in range.
- ----------------------------------------------------------------------------*/
-
- OSErr GetGroupArticleRange (TGroup *theGroup, Boolean *groupExists)
- {
- CStr255 groupName;
- NntpErr nntpErr;
- long first, last, count;
- OSErr err = noErr;
-
- *groupExists = true;
- strcpy(groupName, *gGroupNames + theGroup->nameOffset);
- nntpErr = NntpGetGroupInfo(gNewsStream, groupName, &first, &last, &count);
- if (nntpErr != nntpNoErr) {
- if (nntpErr == nntpNoSuchGroupErr) {
- *groupExists = false;
- return noErr;
- } else {
- return ReportServerError(nntpErr);
- }
- }
-
- if (first == 0 && last == 0) {
- /* Special case empty group: set firstMess = lastMess + 1. */
- theGroup->firstMess = theGroup->lastMess + 1;
- err = AgeCache(groupName, 0x7fffffff);
- if (err != noErr) return err;
- } else {
- if (first <= 0) first = 1;
- if (last < first) last = first - 1;
- theGroup->firstMess = first;
- theGroup->lastMess = last;
- err = AgeCache(groupName, first);
- if (err != noErr) return err;
- }
- theGroup->numUnread = theGroup->lastMess - theGroup->firstMess + 1;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetGroupArrayArticleRanges
-
- Query the NNTP server to get current article range info for designated
- groups in a group list array.
-
- Entry: groupArray = handle to group array.
- numGroups = number of groups in group array.
-
- Each group in the array to be updated is marked with status='x'.
-
- Exit: function result = error code.
- Deleted groups are marked with status='d'.
- ----------------------------------------------------------------------------*/
-
- OSErr GetGroupArrayArticleRanges (TGroup **groupArray, short numGroups)
- {
- NntpGroupInfoHandle info = nil;
- NntpErr nntpErr;
- NntpGroupInfoPtr q, qEnd;
- TGroup *p, *pEnd;
- long first, last;
- long numGroupsToUpdate = 0;
- CStr255 groupName;
- OSErr err = noErr;
-
- for (p = *groupArray, pEnd = p + numGroups; p < pEnd; p++)
- if (p->status == 'x') numGroupsToUpdate++;
-
- err = MyNewHandle(numGroupsToUpdate * sizeof(NntpGroupInfo), &info);
- if (err != noErr) goto exit;
-
- for (p = *groupArray, pEnd = p + numGroups, q = *info; p < pEnd; p++) {
- if (p->status == 'x') {
- q->offset = p->nameOffset;
- q++;
- }
- }
-
- nntpErr = NntpGetMultipleGroupInfo(gNewsStream, info, numGroupsToUpdate, gGroupNames);
- if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
-
- MyHLock(info);
- for (q = *info, qEnd = q + numGroupsToUpdate, p = *groupArray; q < qEnd; q++) {
- while (p->status != 'x') p++;
- if (q->ok) {
- first = q->first;
- last = q->last;
- strcpy(groupName, *gGroupNames + q->offset);
- if (first == 0 && last == 0) {
- /* Special case empty group: set firstMess = lastMess + 1. */
- p->firstMess = p->lastMess + 1;
- err = AgeCache(groupName, 0x7fffffff);
- if (err != noErr) goto exit;
- } else {
- if (first <= 0) first = 1;
- if (last < first) last = first - 1;
- p->firstMess = first;
- p->lastMess = last;
- err = AgeCache(groupName, first);
- if (err != noErr) goto exit;
- }
- p->numUnread = p->lastMess - p->firstMess + 1;
- } else {
- p->status = 'd';
- }
- p++;
- }
- MyDisposeHandle(info);
- return noErr;
-
- exit:
-
- MyDisposeHandle(info);
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- GetHeaders
-
- Get header lines from the news server.
-
- Entry: groupName = the group name.
- headerName = the header name.
- first = first article number.
- last = last article number.
-
- Exit: function result = error code.
- *headers = handle to array of THeader records, or nil if
- group does not exist.
- *strings = handle to header strings.
- *numHeaders = number of headers.
- ----------------------------------------------------------------------------*/
-
- OSErr GetHeaders (char *groupName, char *headerName, long first, long last,
- THeader ***headers, Handle *strings, long *numHeaders)
- {
- NntpErr nntpErr;
- NntpHeaderInfoHandle info;
- Handle str;
- long num;
- unsigned char *p, *pEnd, *q;
-
- nntpErr = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
- nil, nil, nil, &info, &str, &num);
- if (nntpErr != nntpNoErr) {
- if (nntpErr == nntpNoSuchGroupErr) {
- *headers = nil;
- return noErr;
- } else {
- return ReportServerError(nntpErr);
- }
- }
-
- /* Filter unprintable characters. */
-
- p = (unsigned char*)*str;
- pEnd = p + MyGetHandleSize(str);
- while (p < pEnd) {
- q = p;
- while (*p != 0) {
- if (*p >= ' ') {
- *q++ = *p++;
- } else {
- p++;
- }
- }
- *q = 0;
- p++;
- }
-
- MapLatin1ToMacHandle(str);
-
- *headers = (THeader**)info;
- *strings = str;
- *numHeaders = num;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- SearchHeaders
-
- Search header lines and returns matches. The search is for any
- substring and is case-insensitive. The XPAT command is used if the
- server supports it and the "use XPAT" preference is turned on.
- Otherwise, we do it the hard way.
-
- Entry: groupName = the group name.
- headerName = the header name.
- first = first article number.
- last = last article number.
- pattern = search string. WARNING: This string is modified
- by the function.
-
- Exit: function result = error code.
- *headers = handle to array of THeader records, or nil if
- group does not exist.
- *numHeaders = number of headers.
- ----------------------------------------------------------------------------*/
-
- static void BuildXPAT (char *pattern, char *regExp, short regExpLen)
- {
- unsigned char *p, *q, *qEnd, *e;
- unsigned char equivs[256];
- short numEquiv;
-
- p = (unsigned char*)pattern;
- q = (unsigned char*)regExp;
- qEnd = q + regExpLen;
-
- if (q >= qEnd) goto exit;
- *q++ = '*';
- while (*p != 0) {
- GetEquivalentCharacters(*p, equivs, &numEquiv);
- if (numEquiv > 1) {
- if (q >= qEnd) goto exit;
- *q++ = '[';
- }
- e = equivs;
- while (*e != 0) {
- if (*e == '*' || *e == '?' || *e == '\\' || *e == '[' || *e == ']') {
- if (q >= qEnd) goto exit;
- *q++ = '\\';
- }
- if (q >= qEnd) goto exit;
- *q++ = *e++;
- }
- if (numEquiv > 1) {
- if (q >= qEnd) goto exit;
- *q++ = ']';
- }
- p++;
- }
- if (q >= qEnd) goto exit;
- *q++ = '*';
- if (q >= qEnd) goto exit;
- *q = 0;
- MapMacToLatin1Ptr(regExp, (char*)q - regExp);
- return;
-
- exit:
-
- *(regExp + regExpLen - 1) = 0;
- return;
- }
-
- static Boolean MatchPattern (char *pattern, char *string)
- {
- CStr255 str;
-
- MapLatin1ToMacStr(string, str);
- return MyIsASubstring(str, pattern);
- }
-
- OSErr SearchHeaders (char *groupName, char *headerName, long first, long last,
- char *pattern, THeader ***headers, short *numHeaders)
- {
- NntpErr nntpErr;
- NntpHeaderInfoHandle info;
- Handle str;
- long num;
-
- nntpErr = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
- pattern, BuildXPAT, MatchPattern, &info, &str, &num);
- if (nntpErr != nntpNoErr) {
- if (nntpErr == nntpNoSuchGroupErr) {
- *headers = nil;
- return noErr;
- } else {
- return ReportServerError(nntpErr);
- }
- }
-
- MyDisposeHandle(str);
- *headers = (THeader**)info;
- *numHeaders = num;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- DoGetServerInfo
-
- Handle the "Get Server Info" command.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- OSErr DoGetServerInfo (void)
- {
- char str[1000];
- char *p, *pEnd, *q;
- Handle serverHelpResponse = nil;
- Handle serverInfoText = nil;
- Str255 dateStr, timeStr, versStr, title;
- CStr255 addrStr;
- unsigned long rawSecs;
- unsigned long ipAddr;
- CStr255 helloMsg;
- NntpErr nntpErr;
- CStr255 cmd, helpCmdResponse, fmt;
- long num;
- OSErr err = noErr;
- WindowPtr wind;
-
- MyICReadSharedPrefs(kICNNTPHost);
-
- GetPString(kStrServerInfoWindTitle, title);
- if (CheckTextWindowAlreadyOpen(title)) return noErr;
-
- HiliteMenu(0);
-
- GetDateTime(&rawSecs);
- IUDateString(rawSecs, abbrevDate, dateStr);
- IUTimeString(rawSecs, false, timeStr);
- err = GetVersionString(versStr);
- if (err != noErr) goto exit;
- NntpGetIPAddr(gNewsStream, &ipAddr);
- NntpGetHello(gNewsStream, helloMsg);
- sprintf(addrStr, "%ld.%ld.%ld.%ld", (ipAddr >> 24) & 0xff, (ipAddr >> 16) & 0xff,
- (ipAddr >> 8) & 0xff, ipAddr & 0xff);
- nntpErr = NntpGetHelp(gNewsStream, &serverHelpResponse);
- if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
- NntpGetServerErrInfo(cmd, &num, helpCmdResponse);
- GetCString(kStrServerInfoFmt, fmt);
- p2cstr(dateStr);
- p2cstr(timeStr);
- p2cstr(versStr);
- p2cstr(gPrefs.newsServerName);
- sprintf(str, fmt, dateStr, timeStr, versStr, gPrefs.newsServerName,
- addrStr, helloMsg, helpCmdResponse);
- c2pstr((char*)gPrefs.newsServerName);
-
- p = *serverHelpResponse + MyGetHandleSize(serverHelpResponse) - 1;
- while (p > *serverHelpResponse && *p == CR) p--;
- MySetHandleSize(serverHelpResponse, p - *serverHelpResponse + 1);
-
- for (p = *serverHelpResponse, pEnd = p + MyGetHandleSize(serverHelpResponse);
- p < pEnd;
- p++)
- {
- if (*p == '<') {
- q = p+1;
- while (q < pEnd && *q != '>' && *q != '@' && *q != CR) q++;
- if (q < pEnd && *q == '@') {
- q++;
- while (q < pEnd && *q != '>' && *q != CR) q++;
- if (q < pEnd && *q == '>') {
- *p = ' ';
- *q = ' ';
- break;
- }
- }
- }
- }
-
- err = MyPtrToHand(str, &serverInfoText, strlen(str));
- if (err != noErr) goto exit;
- err = MyHandAndHand(serverHelpResponse, serverInfoText);
- if (err != noErr) goto exit;
- MyDisposeHandle(serverHelpResponse);
- serverHelpResponse = nil;
- MapLatin1ToMacHandle(serverInfoText);
- err = MakeNewTextWindow(title, 0, nil, serverInfoText, &wind);
- if (err != noErr) goto exit;
- MyDisposeHandle(serverInfoText);
- return noErr;
-
- exit:
-
- MyDisposeHandle(serverHelpResponse);
- MyDisposeHandle(serverInfoText);
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- ResetNewsServerOptions
-
- Reset the server options on the news server stream. This function must
- be called whenever the options change (e.g., when the user changes them
- in the prefs dialog).
- ----------------------------------------------------------------------------*/
-
- void ResetNewsServerOptions (void)
- {
- NntpStreamOptions options;
-
- SetServerOptions(&options);
- if (gNewsStream != nil) NntpSetStreamOptions(gNewsStream, &options);
- }
-
-
-
- /*----------------------------------------------------------------------------
- Reauthenticate
-
- Reauthenticate the user after a change in username or password.
-
- Exit: function result = error code.
- ----------------------------------------------------------------------------*/
-
- OSErr ReAuthenticate (void)
- {
- NntpStreamHandle oldStream;
- OSErr err = noErr;
-
- if (gNewsStream == nil) return noErr;
- if (!gPrefs.authAtStartup) {
- NntpAbort(gNewsStream);
- } else {
- oldStream = gNewsStream;
- gNewsStream = nil;
- err = StartNNTP();
- if (err == noErr) {
- NntpClose(oldStream);
- } else {
- gNewsStream = oldStream;
- }
- }
- return err;
- }
-